/*
 * Licensed this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.boapi.shop;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Locale;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.boapi.core.IChecknumber;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * Global Trade Item Number [GTIN] is a 13 digits number - 
 * or as ISBN 13 with an X as check digit.
 * Since 2005 EAN International and American UCC merge to GS1 and also
 * EAN and UPC now named as GTIN.
 * @see http://www.gs1-germany.de/
 * @author Sebastian Ritter
 * @version 3.0.0.0
 */
public class GTIN implements IChecknumber {

  /**
   * Create an GTIN from ISBN-13
   * @param isbn
   * @return GTIN
   */
  public static GTIN parse (ISBN13 isbn) {
    GTIN gtin = new GTIN ();
    gtin.setCompleteValue (new String[]{isbn.getNumber ()});
    return gtin;
  }

    /**
     * Global Trade Item Number 
     */
    private String number;
    /**
     * Set the GTIN
     * @param ean
     */
    public void setCompleteValue (String[] gtin) {
      StringBuilder nummer = new StringBuilder ();
      for (char c : gtin[0].toUpperCase().toCharArray()) {
        if (Character.isDigit (c) || c == 'X') {
          nummer.append (c);
        }
        else if (c != '-') {
          throw new IllegalArgumentException ("Illegal character in GTIN "+ gtin[0]);
        }
      }
      while (nummer.length() < 13) {
        nummer.insert (0, '0');
      }
      number = nummer.toString();
      this.setChecknumber ('R');
    }
    /**
     * Same use as param keyword under C#
     */
    public void setCompleteValue (String gtin) {
    	this.setCompleteValue(new String[]{gtin});
    }

    
    /**
     * The number of GTIN
     * @return GTIN number
     */
    public String getNumber () {
      return this.number;
    }
    /** The check digit
     */
    private char checknumber = 'R';
    /**
     * Set the check digit
     * @param checkdigit
     */
    void setChecknumber (char checkdigit) {
      this.checknumber = checkdigit;
    }

    /**
     * Get the check digit
     * @return check digit
     */
    char getChecknumber () {
      if (this.checknumber == 'R') {
        this.isChecknumberCorrect ();
      }
      return this.checknumber;
    }
    /**
     * Is the checknumber correct
     * @return true if check digit is correct otherwise false
     */
    public boolean isChecknumberCorrect () {
      int[] factors = new int[] { 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3};
      int[] digits = new int[factors.length];
      for (int i = 0; i < digits.length; i++) {
        digits[i] = Integer.parseInt(this.getNumber().substring (i, i+1));
      }
      int sum = 0;
      for (int i = 0; i < digits.length; i++) {
        sum += digits[i] * factors[i];
      }
      int checknumber = 10-(sum % 10);
      this.setChecknumber (("" + checknumber).charAt(0));
      return this.getChecknumber() == this.getNumber().charAt(12);
    }

    /**
     * Save the i18n detail informations from GTIN 
     */
    private static HashMap<Locale,HashMap<String,String>> localization;
    /**
     * The default value for GTIN information of locale
     * Note: Default value can change with attribute default='default' in GTIN.xml.
     */
    private static String defaultGTINLocale = ""; // if you want to change do it in GTIN.xml!

    /**
     * Load the i18n detail informations for setting locale language
     * @see java.util.Locale.setDefault(java.util.Locale)
     * @throws IOException if not found or XML error
     */
    private static void loadGTINLocale () throws IOException {
     	GTIN.localization = new HashMap<Locale, HashMap<String,String>>();

   	    InputStream xmlInputStream = null;
     	try{ 
	    	 final String lang = Locale.getDefault().getLanguage();
	    	 final DocumentBuilderFactory domDBF = DocumentBuilderFactory.newInstance();
	    	 final DocumentBuilder domDB = domDBF.newDocumentBuilder();
	    	 xmlInputStream = GTIN.class.getResourceAsStream("/GTIN.xml");
	    	 final Document gtin = domDB.parse(xmlInputStream);
	    	 // search right language
	    	 final NodeList nl = gtin.getElementsByTagName("gtin");
	    	 Node searchedLangGTINTag = null;
	    	 for (int i = 0; i < nl.getLength(); i++) {
	    	   final Node gtinTag = nl.item(i);
	    	   final String gtinLang = gtinTag.getAttributes().getNamedItem("lang").getNodeValue();
	    	   if (lang.equals(gtinLang)) {
	    		   searchedLangGTINTag = gtinTag;
	    		   i = nl.getLength(); // skip all other
	    	   }
	    	   else if (Locale.ENGLISH.getLanguage().equals(gtinLang)) {
	    		   searchedLangGTINTag = gtinTag; // set Default value if language not declared in xml
	    	   }
	    	 }
	    	 // language found, loading informations
	    	 Node details = null;
	    	 for (int i = 0; i < searchedLangGTINTag.getChildNodes().getLength();i++) {
	    		 if ("details".equals(searchedLangGTINTag.getChildNodes().item(i).getNodeName())) {
	    			 details = searchedLangGTINTag.getChildNodes().item(i);
	    			 i = searchedLangGTINTag.getChildNodes().getLength();
	    		 }
	    	 }
	    	 final NodeList detail = details.getChildNodes();
	    	 GTIN.localization.put(Locale.getDefault(), new HashMap<String, String>());
	    	 for (int i = 0; i < detail.getLength(); i++) {
	    	   if (null != detail.item(i) && "detail".equals(detail.item(i).getNodeName())) {
                   if (null != detail.item(i).getAttributes() && null != detail.item(i).getAttributes().getNamedItem("locale")) {	    		   
		    		   final String gtinLocale = detail.item(i).getAttributes().getNamedItem("locale").getNodeValue();
		    		   if (null != detail.item(i).getAttributes().getNamedItem("default")){
		    			   GTIN.defaultGTINLocale = gtinLocale;
		    		   }
		    		   final NodeList startsWithList = detail.item(i).getChildNodes();
		    		   for (int j = 0; j < startsWithList.getLength(); j++) {
		    			   final String startsWith = startsWithList.item(j).getTextContent().trim();
		    			   if (0 < startsWith.length()) {
		    				   GTIN.localization.get(Locale.getDefault()).put(startsWith, gtinLocale);
		    			   }
		    		   }
                   }
	    	   }
	    	 }
     	}
     	catch (ParserConfigurationException e) {
 		  throw new IOException(e);
 	  	}
 	  	catch (SAXException e){
 		  throw new IOException(e);
 	  	}
 	  	finally {
 	  	  if (null != xmlInputStream) {
 	  		xmlInputStream.close ();
 	  	  }
 	  	}
    }
    /**
     * Return an internationalized GTIN localization or empty string if unknown
     * @return location
     */
    public String getLocation () throws Exception {
      if (GTIN.localization == null) {
    	  GTIN.loadGTINLocale();
      }
      String local = defaultGTINLocale;
      
      String startsWith = this.getNumber ().substring(0, 4); // change for ISMN
      if (null != GTIN.localization.get(Locale.getDefault()).get(startsWith)) {
    	  local = GTIN.localization.get(Locale.getDefault()).get(startsWith);
      }
      else {
        startsWith = this.getNumber ().substring(0, 3);
        if (null != GTIN.localization.get(Locale.getDefault()).get(startsWith)) {
     	  local = GTIN.localization.get(Locale.getDefault()).get(startsWith);
        }
        else {
    	  startsWith = this.getNumber ().substring(0, 2);
          if (null != GTIN.localization.get(Locale.getDefault()).get(startsWith)) {
        	  local = GTIN.localization.get(Locale.getDefault()).get(startsWith);
          }
          else {
        	  startsWith = this.getNumber ().substring(0, 1);
              if (null != GTIN.localization.get(Locale.getDefault()).get(startsWith)) {
            	  local = GTIN.localization.get(Locale.getDefault()).get(startsWith);
              }
              else {} // local has the default value
          }
        }
      }
      return local;
    }

    /**
     * If GTIN starts with 45 or 49 it is also called Japanese Article Number
     * @return GTIN is also Japanese Article Number
     */
    public boolean isJAN () {
      boolean ok = false;
      if (this.getNumber ().startsWith ("45") ||
          this.getNumber ().startsWith ("49")) {
        ok = true;
      }
      return ok;
    }
}
